Skip to content

feat: 添加自动更新功能(支持备份与回滚)#8697

Open
Dr1985 wants to merge 4 commits into
AstrBotDevs:masterfrom
Dr1985:master
Open

feat: 添加自动更新功能(支持备份与回滚)#8697
Dr1985 wants to merge 4 commits into
AstrBotDevs:masterfrom
Dr1985:master

Conversation

@Dr1985

@Dr1985 Dr1985 commented Jun 9, 2026

Copy link
Copy Markdown

Description

本 PR 实现了 #8686 中提出的自动更新功能需求。启用后,AstrBot 将定期检查新版本,自动下载并安装更新,在应用更新前创建备份,并在更新失败时自动回滚。

Features

  • 定期检查更新:可配置的 cron 调度(默认:每天凌晨 3 点)

  • 自动安装:检测到新版本时,机器人可自动下载并应用更新

  • 更新前备份:在应用更新前自动创建完整的数据备份

  • 自动回滚:如果更新失败,机器人会自动从更新前的备份中恢复

  • 手动回滚:可通过控制台 (Dashboard) API 手动恢复任何备份

  • 备份自动清理:旧的自动更新备份将在可配置的保留期后自动删除(默认:14 天)

  • 控制台 API:提供完整的 REST API,用于管理自动更新设置、查看历史记录、列出备份列表以及触发手动回滚

Configuration

以下设置位于 cmd_config.jsonauto_update 键下(也可通过 WebUI 进行编辑):

|键名 (Key)|类型 (Type)|默认值 (Default)|描述 (Description)| |---|---|---|---|
|enabled|bool|false|启用自动更新|
|cron_expression|string|"0 3 * * *"|用于检查更新的 Cron 表达式| |auto_install|bool|false|自动安装更新(false = 仅通知)| |backup_before_update|bool|true|应用更新前创建备份|
|backup_retention_days|int|14|自动更新备份保留天数|
|consider_prerelease|bool|false|包含 alpha/beta/rc 版本| |timezone|string|null|用于 Cron 调度的时区|

Dashboard API Endpoints

|请求方法 (Method)|路径 (Path)|描述 (Description)| |---|---|---|
|GET|/api/update/auto-update/settings|获取当前自动更新设置| |POST|/api/update/auto-update/settings/update|更新自动更新设置| |POST|/api/update/auto-update/check|触发立即检查更新|
|GET|/api/update/auto-update/history|查看自动更新事件历史记录| |GET|/api/update/auto-update/backups|列出可用的备份文件| |POST|/api/update/auto-update/rollback|手动回滚到指定备份|

Files Changed

  • 新增: astrbot/core/auto_update/__init__.py — 模块导出

  • 新增: astrbot/core/auto_update/auto_updater.pyAutoUpdateManager

  • 修改: astrbot/core/config/default.py — 添加了 auto_update 配置 schema

  • 修改: astrbot/core/core_lifecycle.py — 在启动期间初始化 AutoUpdateManager

  • 修改: astrbot/dashboard/routes/update.py — 添加了 6 个自动更新 API 端点

How It Works

启动时,如果 auto_update.enabledtrue,管理器将注册以下 cron 任务:

  • 根据配置的调度执行的更新检查任务

  • 备份清理任务(每天凌晨 3:07)

当检查任务触发时:

  1. 调用 AstrBotUpdator.check_update() 请求 release API。

  2. 如果没有可用更新,则记录日志并写入历史记录。

  3. 如果 auto_installfalse,则仅记录“发现可用更新”。

  4. 如果 auto_installtrue

    • 通过 AstrBotExporter.export_all() 创建完整备份。

    • 通过 AstrBotUpdator.update() 下载并解压更新。

    • 如果成功:重启进程。

    • 如果失败:通过 AstrBotImporter.import_all() 从备份中恢复。

  5. 所有事件均记录在 data/auto_update_history.json 中。

Testing

  • 在配置中设置 auto_update.enabled = trueauto_update.auto_install = false

  • 启动 AstrBot — 验证 cron 任务已成功调度。

  • 通过 POST /api/update/auto-update/check 触发手动检查。

  • 验证历史记录是否正确显示了检查结果。

  • 测试完整流程:设置 auto_install = true,Mock (模拟) 一次 release 检查以返回新版本,并验证 备份 → 安装 → 重启 的完整顺畅流程。

Related Issue

Closes #8686

Modifications / 改动点

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果


Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Add configurable automatic update management with backup and rollback support, wired into core lifecycle and exposed via dashboard APIs.

New Features:

  • Expose REST API endpoints for managing auto-update settings, triggering checks, viewing history, listing backups, and performing manual rollbacks.
  • Introduce an auto_update configuration schema for enabling scheduled update checks, automatic installation, backups, and retention settings.
  • Initialize an AutoUpdateManager in the core lifecycle to coordinate scheduled update checks, backups, and rollback operations.

Enhancements:

  • Integrate the auto-update manager with the existing cron and update infrastructure within the core lifecycle.

### **Description**

本 PR 实现了 AstrBotDevs#8686 中提出的自动更新功能需求。启用后,AstrBot 将定期检查新版本,自动下载并安装更新,在应用更新前创建备份,并在更新失败时自动回滚。

### **Features**

- **定期检查更新**:可配置的 cron 调度(默认:每天凌晨 3 点)

- **自动安装**:检测到新版本时,机器人可自动下载并应用更新

- **更新前备份**:在应用更新前自动创建完整的数据备份

- **自动回滚**:如果更新失败,机器人会自动从更新前的备份中恢复

- **手动回滚**:可通过控制台 (Dashboard) API 手动恢复任何备份

- **备份自动清理**:旧的自动更新备份将在可配置的保留期后自动删除(默认:14 天)

- **控制台 API**:提供完整的 REST API,用于管理自动更新设置、查看历史记录、列出备份列表以及触发手动回滚

### **Configuration**

以下设置位于 `cmd_config.json` 的 `auto_update` 键下(也可通过 WebUI 进行编辑):

|**键名 (Key)**|**类型 (Type)**|**默认值 (Default)**|**描述 (Description)**|
|---|---|---|---|
|`enabled`|`bool`|`false`|启用自动更新|
|`cron_expression`|`string`|`"0 3 * * *"`|用于检查更新的 Cron 表达式|
|`auto_install`|`bool`|`false`|自动安装更新(false = 仅通知)|
|`backup_before_update`|`bool`|`true`|应用更新前创建备份|
|`backup_retention_days`|`int`|`14`|自动更新备份保留天数|
|`consider_prerelease`|`bool`|`false`|包含 alpha/beta/rc 版本|
|`timezone`|`string`|`null`|用于 Cron 调度的时区|

### **Dashboard API Endpoints**

|**请求方法 (Method)**|**路径 (Path)**|**描述 (Description)**|
|---|---|---|
|`GET`|`/api/update/auto-update/settings`|获取当前自动更新设置|
|`POST`|`/api/update/auto-update/settings/update`|更新自动更新设置|
|`POST`|`/api/update/auto-update/check`|触发立即检查更新|
|`GET`|`/api/update/auto-update/history`|查看自动更新事件历史记录|
|`GET`|`/api/update/auto-update/backups`|列出可用的备份文件|
|`POST`|`/api/update/auto-update/rollback`|手动回滚到指定备份|

### **Files Changed**

- **新增**: `astrbot/core/auto_update/__init__.py` — 模块导出

- **新增**: `astrbot/core/auto_update/auto_updater.py` — `AutoUpdateManager` 类

- **修改**: `astrbot/core/config/default.py` — 添加了 `auto_update` 配置 schema

- **修改**: `astrbot/core/core_lifecycle.py` — 在启动期间初始化 `AutoUpdateManager`

- **修改**: `astrbot/dashboard/routes/update.py` — 添加了 6 个自动更新 API 端点

### **How It Works**

启动时,如果 `auto_update.enabled` 为 `true`,管理器将注册以下 cron 任务:

- 根据配置的调度执行的**更新检查任务**

- **备份清理任务**(每天凌晨 3:07)

当检查任务触发时:

1. 调用 `AstrBotUpdator.check_update()` 请求 release API。

2. 如果没有可用更新,则记录日志并写入历史记录。

3. 如果 `auto_install` 为 `false`,则仅记录“发现可用更新”。

4. 如果 `auto_install` 为 `true`:

    - 通过 `AstrBotExporter.export_all()` 创建完整备份。

    - 通过 `AstrBotUpdator.update()` 下载并解压更新。

    - **如果成功**:重启进程。

    - **如果失败**:通过 `AstrBotImporter.import_all()` 从备份中恢复。

5. 所有事件均记录在 `data/auto_update_history.json` 中。

### **Testing**

- 在配置中设置 `auto_update.enabled = true` 且 `auto_update.auto_install = false`。

- 启动 AstrBot — 验证 cron 任务已成功调度。

- 通过 `POST /api/update/auto-update/check` 触发手动检查。

- 验证历史记录是否正确显示了检查结果。

- **测试完整流程**:设置 `auto_install = true`,Mock (模拟) 一次 release 检查以返回新版本,并验证 `备份 → 安装 → 重启` 的完整顺畅流程。

### **Related Issue**

Closes AstrBotDevs#8686
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. area:webui The bug / feature is about webui(dashboard) of astrbot. feature:updater The bug / feature is about astrbot updater system labels Jun 9, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The helper _get_auto_update_mgr is declared async but never awaits anything and simply returns a tuple; consider making it a synchronous private method and having the handlers use it directly to avoid unnecessary async overhead and to simplify typing.
  • In rollback_auto_update, backup_path from the request is passed through directly; it would be safer to validate/sanitize this path (e.g., restrict to a known backup directory and prevent .. traversal) before invoking rollback_to_backup.
  • The new auto-update endpoints return generic error strings (e.g., 'Auto-update manager is not available.' or raw exception text); consider normalizing these into a small set of user-facing messages while logging detailed exceptions to avoid leaking internal details through the API.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The helper `_get_auto_update_mgr` is declared `async` but never awaits anything and simply returns a tuple; consider making it a synchronous private method and having the handlers use it directly to avoid unnecessary async overhead and to simplify typing.
- In `rollback_auto_update`, `backup_path` from the request is passed through directly; it would be safer to validate/sanitize this path (e.g., restrict to a known backup directory and prevent `..` traversal) before invoking `rollback_to_backup`.
- The new auto-update endpoints return generic error strings (e.g., 'Auto-update manager is not available.' or raw exception text); consider normalizing these into a small set of user-facing messages while logging detailed exceptions to avoid leaking internal details through the API.

## Individual Comments

### Comment 1
<location path="astrbot/dashboard/routes/update.py" line_range="415-424" />
<code_context>
+        if err:
+            return err
+        try:
+            data = await request.json
+            backup_path = data.get("backup_path", "")
+            if not backup_path:
+                return Response().error("Missing backup_path parameter.").__dict__
</code_context>
<issue_to_address>
**issue:** Handle missing or invalid JSON body before accessing `backup_path`.

If the client sends no body or a non-JSON body, `data` may be `None` or not a mapping, so `data.get(...)` can raise. Add a guard (e.g., `if not data: ...` or a type check) before accessing `backup_path` to align with `update_auto_update_settings` and avoid runtime errors.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread astrbot/dashboard/routes/update.py

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an automatic update feature to AstrBot, including configuration defaults, lifecycle integration via AutoUpdateManager, and several dashboard API endpoints for managing settings, checking for updates, and performing rollbacks. Feedback highlights three key areas for improvement: separating configuration metadata from default values in DEFAULT_CONFIG, sanitizing the backup_path parameter in the rollback endpoint to prevent path traversal vulnerabilities, and wrapping the initialization of AutoUpdateManager in a try-except block to ensure that any failure there does not prevent the entire application from starting.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +311 to +351
"auto_update": {
"description": "自动更新设置",
"type": "object",
"properties": {
"enabled": {
"description": "启用自动更新",
"type": "bool",
"hint": "开启后,AstrBot 将定期检查并自动安装更新。",
},
"cron_expression": {
"description": "检查更新的 Cron 表达式",
"type": "string",
"hint": "默认为每天凌晨 3 点检查。",
},
"auto_install": {
"description": "自动安装更新",
"type": "bool",
"hint": "关闭时仅通知有新版本,不自动安装。",
},
"backup_before_update": {
"description": "更新前自动备份",
"type": "bool",
"hint": "开启后,更新前会自动创建完整数据备份。",
},
"backup_retention_days": {
"description": "备份保留天数",
"type": "int",
"hint": "自动更新创建的备份将在此天数后被自动删除。默认 14 天。",
},
"consider_prerelease": {
"description": "包含预发布版本",
"type": "bool",
"hint": "开启后,也会检查 alpha/beta/rc 等预发布版本。",
},
"timezone": {
"description": "时区",
"type": "string",
"hint": "用于 Cron 调度的时区,例如 Asia/Shanghai。留空使用系统时区。",
},
},
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

DEFAULT_CONFIG 中,应该只定义配置项的默认值,而不是配置项的元数据/Schema(如 descriptiontypeproperties 等)。

如果将 Schema 直接放入 DEFAULT_CONFIG,会导致 astrbot_config.get("auto_update") 返回 Schema 字典,而不是实际的默认配置值。例如,当代码尝试获取 enabled 时,会得到一个表示 Schema 的字典(在 Python 中被评估为 True),从而导致逻辑错误或类型崩溃。

解决方案

  1. DEFAULT_CONFIG 中仅保留默认值。
  2. auto_update 的 Schema 元数据移至 CONFIG_METADATA_3_SYSTEM(系统配置组)中。
    "auto_update": {
        "enabled": False,
        "cron_expression": "0 3 * * *",
        "auto_install": False,
        "backup_before_update": True,
        "backup_retention_days": 14,
        "consider_prerelease": False,
        "timezone": None,
    },

Comment thread astrbot/dashboard/routes/update.py Outdated
exporter=exporter,
importer=importer,
)
await self.auto_update_manager.initialize()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

自动更新管理器(AutoUpdateManager)的初始化属于辅助功能。如果其初始化过程中抛出异常(例如:Cron 表达式解析失败、时区配置错误或数据库读取异常),直接 await 会导致整个 AstrBot 核心生命周期初始化失败,从而使机器人无法启动。

建议将 await self.auto_update_manager.initialize() 包裹在 try-except 块中,捕获异常并记录错误日志,以确保即使自动更新功能初始化失败,机器人的核心功能仍能正常启动。

        try:
            await self.auto_update_manager.initialize()
        except Exception as e:
            logger.error(f"Failed to initialize AutoUpdateManager: {e}", exc_info=True)

Dr1985 and others added 3 commits June 9, 2026 13:07
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
## 描述

### 问题

当 LLM 通过内置工具 `send_message_to_user` 发送消息时,无论消息内容多长,都不会触发 QQ 合并转发消息。这是因为 `send_message_to_user` 工具直接调用 `Context.send_message()` 发送消息,完全绕过了事件处理管线(Pipeline)。而 `forward_threshold` 的检查逻辑位于管线中的 `ResultDecorateStage` 阶段。

**两条发送路径对比**:

- **普通 LLM 回复**:经过完整管线 → `ResultDecorateStage` 检测字数 → 超阈值时包装为 `Node` → aiocqhttp 适配器调用 `send_group_forward_msg`
- **`send_message_to_user`**:`Context.send_message()` → 直接调用平台适配器的 `send_by_session()` → 跳过所有管线阶段(包括 `forward_threshold`、速率限制、内容安全检查等)

### 修复方案

在 `SendMessageToUserTool.call()` 方法中,在调用 `send_message()` 之前,添加与 `ResultDecorateStage` 相同的 `forward_threshold` 检查逻辑。当目标平台为 `aiocqhttp` 且纯文本字数超过阈值时,将消息链包装为 `Node` 组件,从而触发合并转发。

### 修改文件

| 文件 | 修改内容 |
|---|---|
| `astrbot/core/tools/message_tools.py` | 在 `SendMessageToUserTool.call()` 中添加 `forward_threshold` 检查(+21 行) |

### 具体修改

在 `send_message()` 调用前插入以下逻辑:

```python
# Apply forward_threshold for aiocqhttp platform.
# The normal reply path applies this check in ResultDecorateStage,
# but tool-sent messages bypass the pipeline, so we replicate it here.
# See AstrBotDevs#8678
if target_session.platform_name == "aiocqhttp":
    cfg = context.context.context.get_config(
        umo=context.context.event.unified_msg_origin
    )
    threshold = cfg.get("platform_settings", {}).get("forward_threshold", 1500)
    word_cnt = 0
    for comp in components:
        if isinstance(comp, Comp.Plain):
            word_cnt += len(comp.text)
    if word_cnt > threshold:
        node = Comp.Node(
            uin=context.context.event.get_self_id(),
            name="AstrBot",
            content=[*components],
        )
        components = [node]
```

### 测试方式

1. 使用 aiocqhttp(OneBot V11)平台适配器
2. 将 `platform_settings.forward_threshold` 设置为较小的值(如 100)
3. 让 LLM 调用 `send_message_to_user` 工具发送超过阈值字数的纯文本内容
4. 观察消息以合并转发形式发出,而非普通消息

### 关联 Issue

Closes AstrBotDevs#8678
@mjy1113451

Copy link
Copy Markdown

能不能不控制更新?那别人不想更新怎么办

@Dr1985

Dr1985 commented Jun 10, 2026

Copy link
Copy Markdown
Author

能不能不控制更新?那别人不想更新怎么办

有道理,我来改一改。

@mjy1113451

mjy1113451 commented Jun 10, 2026 via email

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:webui The bug / feature is about webui(dashboard) of astrbot. feature:updater The bug / feature is about astrbot updater system size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 自动更新

2 participants